﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Workflow.Activities;
using System.Workflow.ComponentModel;
using BoldBrick.OneIntranet.HR.DataObjects;
using BoldBrick.OneIntranet.HR.DataObjects.ContentTypes;
using BoldBrick.OneIntranet.HR.DataObjects.Lists.WorkflowHistoryListData;
using BoldBrick.OneIntranet.HR.Resources;
using BoldBrick.SharePoint.CommonControls.DataObjects.Base;
using BoldBrick.SharePoint.CommonControls.Utils;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Workflow;
using BoldBrick.SharePoint.CommonControls.Extensions;

namespace BoldBrick.OneIntranet.HR.Workflows.HRWorkflow
{
	public sealed partial class HRWorkflow : SequentialWorkflowActivity
	{
		#region ------ Workflow Properties --------------------------------------------------------

		public Guid WorkflowId = default(Guid);
		public SPWorkflowActivationProperties WorkflowProperties = new SPWorkflowActivationProperties();
		public Int32 UserID = default(Int32);
		private uint language;

		private int workflowItemId;
		private int positionItemId;

		private int currentWorkflowStepIndex;
		private List<HRWorkflowStepData> workflowSteps;

		private HRWorkflowStepData CurrentWorkflowStep
		{
			get
			{
				if (currentWorkflowStepIndex < workflowSteps.Count)
					return workflowSteps[currentWorkflowStepIndex];

				return null;
			}
		}

		public String HistoryDescription = default(String);

		public string CurrentTaskOwnerEmail;
		private string currentTaskOwnerLoginName;

		/// <summary>
		/// List item id bound to the task
		/// </summary>
		public Int32 ListItemId = default(Int32);
		public Guid TaskId = default(Guid);
		public SPWorkflowTaskProperties TaskProperties = new SPWorkflowTaskProperties();
		public SPWorkflowTaskProperties TaskAfterProperties = new SPWorkflowTaskProperties();
		public String TaskOutcome = default(String);

		/// <summary>
		/// User who updates the task
		/// </summary>
		public String TaskExecutor = default(String);

		public String TaskContentTypeId = default(String);

		/// <summary>
		/// Body of the email which is sent to the user with task
		/// </summary>
		public String EmailBody = default(String);

		/// <summary>
		/// Subject of the email which is sent to the user with task
		/// </summary>
		public String EmailSubject = default(String);

		/// <summary>
		/// "No hire" choice value
		/// </summary>
		private string NoHireValue
		{
			get
			{
				return ResourceBuilder.GetResource("Field_CandidateResult_Value_NoHire", language);
			}
		}

		public String LogData = default(String);


		#endregion

	    #region ------ Constructors ---------------------------------------------------------------

	    public HRWorkflow()
	    {
	        InitializeComponent();
	    }

	    #endregion

		#region ------ Workflow Activated ---------------------------------------------------------

		private void OnWorkflowActivated(object sender, ExternalDataEventArgs e)
		{
			UserID = WorkflowProperties.OriginatorUser.ID;

			SPListItem item = WorkflowProperties.Item;
			item[SiteColumns.CandidateResultColumn.InternalName] = null;
			item.Update();

			EnsureData();

			HRWorkflowHelper.SendEmailToCandidate(WorkflowProperties.Web, item, "Email_FirstContact_Subject", "Email_FirstContact_Body");
		}

		private void EnsureData()
		{
			if (workflowSteps != null)
				return;

			language = WorkflowProperties.Web.Language;

			SPListItem itemCandidate = WorkflowProperties.Item;
			SPListItem itemWorkflow = HRWorkflowHelper.GetWorkflowItemByCandidateItem(WorkflowProperties.Web, itemCandidate);

			// setup fields for log data
			this.workflowItemId = itemWorkflow.ID;
			SPFieldLookupValue positionValue = itemCandidate.GetValueObject<SPFieldLookupValue>(SiteColumns.PositionLookupColumn.InternalName);
			this.positionItemId = positionValue.LookupId;

			string errorMessage;
			if (!HRWorkflowHelper.CheckWorkflow(WorkflowProperties.Web, itemWorkflow, out errorMessage))
			{
				throw new HRWorkflowException(errorMessage);
			}

			SPListItemCollection workflowStepItems = HRWorkflowHelper.GetWorkflowStepItems(WorkflowProperties.Web, itemWorkflow);
			

			workflowSteps = new List<HRWorkflowStepData>();
			foreach (SPListItem itemWorkflowStep in workflowStepItems)
			{
				HRWorkflowStepData workflowStepData = new HRWorkflowStepData(WorkflowProperties.Web, itemWorkflowStep);
				workflowSteps.Add(workflowStepData);
			}

			CheckWorkflowSteps();
		}

		private void CheckWorkflowSteps()
		{
			if (this.workflowSteps.Exists(step => !step.TaskOwnerID.HasValue))
			{
				CancelWorkflow();
				string errorMessage = ResourceBuilder.GetResource("HRWorkflow_Error_TaskWithoutOwner", language);
				throw new SPWorkflowException(errorMessage);
			}

		}
		
		#endregion

		#region ------ Tasks ----------------------------------------------------------------------

		private void taskCreated_Handler(object sender, EventArgs e)
		{
			TaskProperties = new SPWorkflowTaskProperties();
			TaskAfterProperties = new SPWorkflowTaskProperties();

			ListItemId = this.WorkflowProperties.ItemId;
			TaskProperties.SendEmailNotification = CurrentWorkflowStep.SendEmail;

			// setup task content type
			TaskContentTypeId = this.CurrentWorkflowStep.TaskContentType.ContentTypeId;

			// create task id
			TaskId = Guid.NewGuid();

			// assign task to user
			CurrentTaskOwnerEmail = null;
			if (this.CurrentWorkflowStep.TaskOwnerID.HasValue)
			{

				SPUser currentTaskOwner = WorkflowProperties.Web.AllUsers.TryGetUserById(this.CurrentWorkflowStep.TaskOwnerID.Value);
				if (currentTaskOwner != null)
				{
					TaskProperties.AssignedTo = currentTaskOwner.LoginName;
					CurrentTaskOwnerEmail = currentTaskOwner.Email;
				}
			}

			// setup task properties
			TaskProperties.Title = this.CurrentWorkflowStep.TaskTitle;
			TaskProperties.Description = this.CurrentWorkflowStep.TaskDescription;

			// set extended properties
			foreach (string fieldName in this.CurrentWorkflowStep.ExtendedProperties.Keys)
			{
				SPField spField = WorkflowProperties.TaskList.Fields.TryGetFieldByStaticName(fieldName);
				if (spField != null)
				{
					TaskProperties.ExtendedProperties[spField.Id] = this.CurrentWorkflowStep.ExtendedProperties[fieldName];
				}
			}

			currentTaskOwnerLoginName = TaskProperties.AssignedTo;

			PrepareLogData();
		}

		private void taskChanged_Handler(object sender, ExternalDataEventArgs e)
		{
			SPUser user;
			try
			{
				user = WorkflowProperties.Web.Users[this.TaskExecutor];
			}
			catch
			{
				user = null;
			}

			if (user != null)
			{
				this.UserID = user.ID;
			}
		}

		private void taskComplete_Handler(object sender, EventArgs e)
		{
			WriteTaskOutcome();

			if (CurrentWorkflowStep.ResultContentType != null)
			{
				PerformUserElevatedAction(CreateResultItem);
			}

			if (CurrentWorkflowStep != null)
			{
				SPContentTypeId workStepContentTypeId = new SPContentTypeId(CurrentWorkflowStep.WorkflowStepContentType.ContentTypeId);
				if (workStepContentTypeId.IsChildOf(ContentTypes.FinalDecisionWorkflowStepContentType.ID))
				{
					PerformUserElevatedAction(SetFinalDecision);
				}
			}

			// go to nex step
			currentWorkflowStepIndex++;
		}

		private void WriteTaskOutcome()
		{
			string resultColumnName = SiteColumns.CandidateResultColumn.GetDisplayName(language);
			object result = GetTaskItemAfterPropertyValue(SiteColumns.CandidateResultColumn.InternalName);

			TaskOutcome = string.Format("{0}: {1}",
				resultColumnName,
				result);
		}

		/// <summary>
		/// Gets value of task item
		/// </summary>
		/// <param name="fieldName">Field of the task item</param>
		/// <returns>Returns value of task item</returns>
		private object GetTaskItemAfterPropertyValue(string fieldName)
		{
			SPField field = WorkflowProperties.TaskList.Fields.TryGetFieldByStaticName(fieldName);
			if (field != null)
			{
				object value = TaskAfterProperties.ExtendedProperties[field.Id];
				if (value != null)
				{
					return field.GetFieldValue(value.ToString());
				}
			}
			return null;
		}

		#endregion

		#region ------ Final Decision -------------------------------------------------------------

		/// <summary>
		/// Sets final decision result to candidate's item
		/// </summary>
		/// <param name="web">Parent web</param>
		private void SetFinalDecision(SPWeb web)
		{
			SPList listCandidates = web.Lists[WorkflowProperties.ListId];
			SPListItem itemCandidate = listCandidates.GetItemById(WorkflowProperties.ItemId);
			object resultValue = GetTaskItemAfterPropertyValue(SiteColumns.CandidateResultColumn.InternalName);
			itemCandidate[SiteColumns.CandidateResultColumn.InternalName] = resultValue;
			itemCandidate.Update();

			FinishWorkflow();

			if (resultValue != null && resultValue.ToString() == NoHireValue)
			{
				HRWorkflowHelper.SendEmailToCandidate(WorkflowProperties.Web, itemCandidate, "Email_NoHireFinalDecision_Subject", "Email_NoHireFinalDecision_Body");
			}
		}

		#endregion

		#region ------ Create Result Item ---------------------------------------------------------

		/// <summary>
		/// Creates result item in Candidates list
		/// </summary>
		/// <param name="web">Parent web of the list</param>
		private void CreateResultItem(SPWeb web)
		{
			SPList listCandidates = web.Lists[WorkflowProperties.ListId];

			// get content types
			SPContentTypeId resultContentTypeId = new SPContentTypeId(CurrentWorkflowStep.ResultContentType.ContentTypeId);
			SPContentType resultContentType = listCandidates.ContentTypes.GetChildContentType(resultContentTypeId);
			if (resultContentType == null)
			{
				string errorMessage = ResourceBuilder.GetResource("HRWorkflow_Error_ResultContentTypeNotFound", language, listCandidates.Title);
				throw new SPWorkflowException(errorMessage);
			}

			// create item with content type
			SPListItem itemResult = listCandidates.AddItem(WorkflowProperties.Item.Folder.ServerRelativeUrl, SPFileSystemObjectType.File);
			itemResult[SharePoint.CommonControls.DataObjects.SiteColumns.ContentTypeColumn.InternalName] = resultContentType.Name;


			// set value from WorkflowStep
			foreach (string fieldName in CurrentWorkflowStep.ExtendedProperties.Keys)
			{
				SPField field = listCandidates.Fields.TryGetFieldByStaticName(fieldName);
				if (field != null)
				{
					itemResult[fieldName] = field.GetFieldValue(CurrentWorkflowStep.ExtendedProperties[fieldName]);
				}
			}
			itemResult[SiteColumns.CancelIfNoHireColumn.InternalName] = CurrentWorkflowStep.CancelIfNoHire;

			// set values from Task
			IEnumerable<string> taskExtraFields = GetTaskExtraFields();
			foreach (string fieldName in taskExtraFields)
			{
				object value = GetTaskItemAfterPropertyValue(fieldName);
				if (value != null)
				{
					itemResult[fieldName] = value;
				}
			}

			itemResult.Update(false);

			// skip all steps if workflow should be cancelled
			if (CurrentWorkflowStep.CancelIfNoHire)
			{
				object result = GetTaskItemAfterPropertyValue(SiteColumns.CandidateResultColumn.InternalName);
				if (result != null && result.ToString() == NoHireValue)
				{
					FinishWorkflow();

					// setup candidate's item (folder)
					SPListItem itemCandidate = listCandidates.GetItemById(WorkflowProperties.ItemId);
					itemCandidate[SiteColumns.CandidateResultColumn.InternalName] = NoHireValue;
					itemCandidate.Update();

					HRWorkflowHelper.SendEmailToCandidate(WorkflowProperties.Web, itemCandidate, "Email_CancelIfNoHireStep_Subject", "Email_CancelIfNoHireStep_Body");
				}
			}
		}

		private void FinishWorkflow()
		{
			currentWorkflowStepIndex = workflowSteps.Count;
		}

		private IEnumerable<string> GetTaskExtraFields()
		{
			SPContentType baseTaskContentType = WorkflowProperties.Web.ContentTypes.GetChildContentType(ContentTypes.HRTaskContentType.ID);
			if (baseTaskContentType == null)
			{
				string errorMessage = ResourceBuilder.GetResource("HRWorkflow_Error_HRTaskNotFound",
					language,
					ContentTypes.HRTaskContentType.GetName(language),
					WorkflowProperties.Web.ServerRelativeUrl);
				throw new SPWorkflowException(errorMessage);
			}

			SPContentType taskContentType = WorkflowProperties.Web.ContentTypes.GetChildContentType(new SPContentTypeId(this.TaskContentTypeId));
			if (taskContentType == null)
			{
				string errorMessage = ResourceBuilder.GetResource("HRWorkflow_Error_TaskContentTypeNotFound",
					language,
					this.TaskContentTypeId,
					WorkflowProperties.Web.ServerRelativeUrl);
				throw new SPWorkflowException(errorMessage);
			}

			string[] baseTaskFields = baseTaskContentType.FieldLinks.OfType<SPFieldLink>().Select(item => item.Name).ToArray();
			string[] taskFields = taskContentType.FieldLinks.OfType<SPFieldLink>().Select(item => item.Name).ToArray();

			// add HR Task columns
			List<string> fields = taskFields.Except(baseTaskFields).ToList();

			foreach (ListColumn listColumn in ContentTypes.HRTaskContentType.GetListColumns())
			{
				fields.Add(listColumn.InternalName);
			}

			return fields;
		}


		#endregion

		#region ------ User Elevated Action -------------------------------------------------------

		/// <summary>
		/// Performs user elevated code if user is found in SharePoint
		/// </summary>
		/// <param name="action">Action to perform</param>
		private void PerformUserElevatedAction(UserElevatedAction action)
		{
			SPUser user = this.WorkflowProperties.Web.AllUsers.TryGetUserByLoginName(this.TaskExecutor);
			if (user != null)
			{
				SPSecurity.RunWithElevatedPrivileges(delegate
				{
					using (SPSite site = new SPSite(WorkflowProperties.SiteId, user.UserToken))
					{
						using (SPWeb web = site.OpenWeb(WorkflowProperties.WebId))
						{
							action(web);
						}
					}
				});
			}
			else
			{
				action(WorkflowProperties.Web);
			}
		}

		private delegate void UserElevatedAction(SPWeb web);

		#endregion

		#region ------ Emails ---------------------------------------------------------------------

		private void sendEmail_Handler(object sender, EventArgs e)
		{
			// setup email
			EmailSubject = ResourceBuilder.GetResource("Email_TaskAssigned_Subject", language, CurrentWorkflowStep.TaskTitle);

			string workflowUrl = UrlHelper.CombineURLPath(this.WorkflowProperties.WebUrl, this.WorkflowProperties.Workflow.StatusUrl);

			string taskUrl = this.WorkflowProperties.TaskList.GetListItemUrl(PAGETYPE.PAGE_DISPLAYFORM, this.TaskAfterProperties.TaskItemId);
			taskUrl = UrlHelper.CombineURLPath(this.WorkflowProperties.WebUrl, taskUrl);

			EmailBody = ResourceBuilder.GetResource("Email_TaskAssigned_Body", language, CurrentWorkflowStep.TaskDescription, workflowUrl, taskUrl);
		}

		#endregion

		#region ------ Conditions -----------------------------------------------------------------

		private void IsWorkflowPending(object sender, ConditionalEventArgs e)
		{
			e.Result = currentWorkflowStepIndex < workflowSteps.Count;
		}

		private void IsTaskPending(object sender, ConditionalEventArgs e)
		{
			object result = GetTaskItemAfterPropertyValue(SiteColumns.CandidateResultColumn.InternalName);

			e.Result = result == null || string.IsNullOrEmpty(result.ToString());
		}

		private void IsTaskOwnerChanged(object sender, ConditionalEventArgs e)
		{
			e.Result = currentTaskOwnerLoginName != this.TaskAfterProperties.AssignedTo;
			if (e.Result)
			{
				// set new task owner
				currentTaskOwnerLoginName = this.TaskAfterProperties.AssignedTo;

				// get email of task owner
				SPUser currentTaskOwner = WorkflowProperties.Web.AllUsers.TryGetUserByLoginName(currentTaskOwnerLoginName);
				if (currentTaskOwner != null)
				{
					CurrentTaskOwnerEmail = currentTaskOwner.Email;
				}
			}
		}

		private void CanSendEmails(object sender, ConditionalEventArgs e)
		{
			e.Result = CurrentWorkflowStep.SendEmail && !string.IsNullOrEmpty(CurrentTaskOwnerEmail);
		}

		#endregion

		#region ------ Error Handling & Log -------------------------------------------------------

		/// <summary>
		/// Cancels workflow
		/// </summary>
		private void CancelWorkflow()
		{
			SPWorkflow spWorkflow = WorkflowProperties.Item.Workflows[this.WorkflowInstanceId];
			SPWorkflowManager.CancelWorkflow(spWorkflow);

		}

		protected override ActivityExecutionStatus HandleFault(ActivityExecutionContext executionContext, Exception exception)
		{
			if (exception != null)
			{
				LogErrorToHistoryList(executionContext, exception.Message);
			}

			return base.HandleFault(executionContext, exception);
		}

		private void LogErrorToHistoryList(ActivityExecutionContext executionContext, string errorMsg)
		{
			ISharePointService sharePointService = executionContext.GetService(typeof(ISharePointService)) as ISharePointService;
			if (sharePointService != null)
			{
				string errorMessage = string.Format("Error in workflow! {0}", errorMsg);
				sharePointService.LogToHistoryList(base.WorkflowInstanceId, SPWorkflowHistoryEventType.WorkflowError, 0, TimeSpan.MinValue, string.Empty, errorMessage, LogData);
			}
		}

		private void PrepareLogData()
		{
			int? workflowStepItemId = null;
			if (CurrentWorkflowStep != null)
			{
				workflowStepItemId = CurrentWorkflowStep.ID;
			}

			WorkflowHistoryListDataValue data = new WorkflowHistoryListDataValue
													{
														WorkflowItemId = this.workflowItemId,
														PositionItemId = this.positionItemId,
														WorkflowStepId = workflowStepItemId
													};
			this.LogData = data.ToString();
		}

		private void logFinish_Handler(object sender, EventArgs e)
		{
			PrepareLogData();
		}

		#endregion
	}
}
